package p2p

import (
	"context"
	"errors"
	"feishup2pclient/msg"
	"fmt"
	"sync"

	"github.com/ipfs/go-datastore"
	"github.com/libp2p/go-libp2p"
	"github.com/libp2p/go-libp2p-core/crypto"
	"github.com/libp2p/go-libp2p-core/host"
	"github.com/libp2p/go-libp2p-core/network"
	"github.com/libp2p/go-libp2p-core/peer"
	dht "github.com/libp2p/go-libp2p-kad-dht"
	libp2pquic "github.com/libp2p/go-libp2p-quic-transport"
	"github.com/libp2p/go-tcp-transport"
	ma "github.com/multiformats/go-multiaddr"
)

const Protocol = "/fs/0.0.1"

// Define Bootstrap Nodes.
var Peers = []string{
	"/ip4/115.227.19.84/tcp/4001/p2p/QmSGeEkpjSEe4PC8PPoDzPxDPpbAWDrmUeSZZhTPDbNVDo",
	"/ip4/118.213.54.135/tcp/4001/p2p/QmVn1thK4f3XdAg3Joz8qhxgs3A7PTK23dtfu5yq52dzKK",
}

var StaticRelays = make([]peer.AddrInfo, 1)

// CreateNode creates an internal Libp2p nodes and returns it and it's DHT Discovery service.
func CreateNode(ctx context.Context, inputKey string, port int, handler network.StreamHandler) (node host.Host, dhtOut *dht.IpfsDHT, err error) {
	// Unmarshal Private Key
	privateKey, err := crypto.UnmarshalPrivateKey([]byte(inputKey))
	if err != nil {
		return
	}
	// log.Info(StaticRelays)
	// var idht *dht.IpfsDHT
	// Create libp2p node
	node, err = libp2p.New(
		libp2p.ListenAddrStrings(listen_addrs(port)...),
		libp2p.Identity(privateKey),
		// libp2p.ForceReachabilityPublic(),
		libp2p.DefaultSecurity,
		libp2p.NATPortMap(),
		libp2p.EnableNATService(),
		libp2p.DefaultMuxers,
		libp2p.EnableRelay(),
		libp2p.Transport(libp2pquic.NewTransport),
		libp2p.Transport(tcp.NewTCPTransport),
		libp2p.FallbackDefaults,
		libp2p.StaticRelays(StaticRelays),
		libp2p.EnableHolePunching(),
		libp2p.EnableAutoRelay(),
		// libp2p.EnableRelayService(),
	)
	dhtOut = dht.NewDHTClient(ctx, node, datastore.NewMapDatastore())

	// Setup Hyprspace Stream Handler
	node.SetStreamHandler(Protocol, handler)
	// Convert Bootstap Nodes into usable addresses.
	BootstrapPeers := make(map[peer.ID]*peer.AddrInfo, len(Peers))
	for _, addrStr := range Peers {
		addr, err := ma.NewMultiaddr(addrStr)
		if err != nil {
			return node, dhtOut, err
		}
		pii, err := peer.AddrInfoFromP2pAddr(addr)
		if err != nil {
			return node, dhtOut, err
		}
		pi, ok := BootstrapPeers[pii.ID]
		if !ok {
			pi = &peer.AddrInfo{ID: pii.ID}
			BootstrapPeers[pi.ID] = pi
		}
		pi.Addrs = append(pi.Addrs, pii.Addrs...)
	}

	for _, _addr := range dht.DefaultBootstrapPeers {
		addr := _addr
		pii, err := peer.AddrInfoFromP2pAddr(addr)
		if err != nil {
			return node, dhtOut, err
		}
		pi, ok := BootstrapPeers[pii.ID]
		if !ok {
			pi = &peer.AddrInfo{ID: pii.ID}
			BootstrapPeers[pi.ID] = pi
		}
		pi.Addrs = append(pi.Addrs, pii.Addrs...)
	}

	// Let's connect to the bootstrap nodes first. They will tell us about the
	// other nodes in the network.
	var wg sync.WaitGroup
	lock := sync.Mutex{}
	count := 0
	wg.Add(len(BootstrapPeers))
	for _, peerInfo := range BootstrapPeers {
		go func(peerInfo *peer.AddrInfo) {
			defer wg.Done()
			err := node.Connect(ctx, *peerInfo)
			if err == nil {
				lock.Lock()
				count++
				msg.SendMessage(fmt.Sprintf("connect bootstrap node success: %s,%s", peerInfo.ID))
				lock.Unlock()
			} else {
				msg.SendMessage(fmt.Sprintf("connect bootstrap node err: %s,%s", err.Error(), peerInfo.ID))
			}
		}(peerInfo)
	}
	wg.Wait()

	if count < 1 {
		return node, dhtOut, errors.New("unable to bootstrap libp2p node")
	}
	// dhtOut.
	return node, dhtOut, nil
}

func listen_addrs(port int) []string {
	addrs := []string{
		"/ip4/0.0.0.0/tcp/%d",
		"/ip4/0.0.0.0/udp/%d/quic",
		"/ip6/::/tcp/%d",
		"/ip6/::/udp/%d/quic",
	}

	for i, a := range addrs {
		addrs[i] = fmt.Sprintf(a, port)
	}

	return addrs
}

func init() {
	maddr, _ := ma.NewMultiaddr("/ip4/115.227.19.84/tcp/8001")
	maddr2, _ := ma.NewMultiaddr("/ip4/115.227.19.84/udp/8001/quic")
	peerId, _ := peer.Decode("QmbaLkSVNUhn8wHKsi8U1GPAisUBxhwDA69zD8kmpHeyoL")
	StaticRelays[0] = peer.AddrInfo{
		ID:    peerId,
		Addrs: []ma.Multiaddr{maddr, maddr2},
	}
}
